using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class LatheGeometry : BufferGeometry
    {
        public class Parameters
        {
            public JsArr<Vector2> points;
            public int segments;
            public double phiStart;
            public double phiLength;
        }
        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public LatheGeometry(JsArr<Vector2> points = null, int segments = 12, double phiStart = 0, double phiLength = Math.PI * 2)
        {
            if (points == null)
            {
                points = new JsArr<Vector2>() { new Vector2(0, -0.5), new Vector2(0.5, 0), new Vector2(0, 0.5) };
            }
            this.type = "LatheGeometry";
            this.parameters = new Parameters()
            {
                points = points,
                segments = segments,
                phiStart = phiStart,
                phiLength = phiLength
            };
            segments = (int)JMath.floor(segments);
            // clamp phiLength so it"s in range of [ 0, 2PI ]
            phiLength = MathUtils.clamp(phiLength, 0, JMath.PI * 2);
            // buffers
            var indices = new JsArr<int>();
            var vertices = new JsArr<double>();
            var uvs = new JsArr<double>();
            var initNormals = new JsArr<double>();
            var normals = new JsArr<double>();
            // helper variables
            var inverseSegments = 1.0 / segments;
            var vertex = new Vector3();
            var uv = new Vector2();
            var normal = new Vector3();
            var curNormal = new Vector3();
            var prevNormal = new Vector3();
            double dx = 0;
            double dy = 0;
            // pre-compute normals for initial "meridian"
            for (int j = 0; j <= (points.length - 1); j++)
            {
                if (j == 0) // special handling for 1st vertex on path
                {
                    dx = points[j + 1].x - points[j].x;
                    dy = points[j + 1].y - points[j].y;
                    normal.x = dy * 1.0;
                    normal.y = -dx;
                    normal.z = dy * 0.0;
                    prevNormal.copy(normal);
                    normal.normalize();
                    initNormals.push(normal.x, normal.y, normal.z);
                }
                else if (j == points.length - 1) // special handling for last Vertex on path
                {
                    initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z);
                }
                else // default handling for all vertices in between
                {
                    dx = points[j + 1].x - points[j].x;
                    dy = points[j + 1].y - points[j].y;
                    normal.x = dy * 1.0;
                    normal.y = -dx;
                    normal.z = dy * 0.0;
                    curNormal.copy(normal);
                    normal.x += prevNormal.x;
                    normal.y += prevNormal.y;
                    normal.z += prevNormal.z;
                    normal.normalize();
                    initNormals.push(normal.x, normal.y, normal.z);
                    prevNormal.copy(curNormal);
                }
            }
            // generate vertices, uvs and normals
            for (int i = 0; i <= segments; i++)
            {
                var phi = phiStart + i * inverseSegments * phiLength;
                var sin = JMath.sin(phi);
                var cos = JMath.cos(phi);
                for (int j = 0; j <= (points.length - 1); j++)
                {
                    // vertex
                    vertex.x = points[j].x * sin;
                    vertex.y = points[j].y;
                    vertex.z = points[j].x * cos;
                    vertices.push(vertex.x, vertex.y, vertex.z);
                    // uv
                    uv.x = i / (double)segments;
                    uv.y = j / (double)(points.length - 1);
                    uvs.push(uv.x, uv.y);
                    // normal
                    var x = initNormals[3 * j + 0] * sin;
                    var y = initNormals[3 * j + 1];
                    var z = initNormals[3 * j + 0] * cos;
                    normals.push(x, y, z);
                }
            }
            // indices
            for (int i = 0; i < segments; i++)
            {
                for (int j = 0; j < (points.length - 1); j++)
                {
                    var baseLen = j + i * points.length;
                    var a = baseLen;
                    var b = baseLen + points.length;
                    var c = baseLen + points.length + 1;
                    var d = baseLen + 1;
                    // faces
                    indices.push(a, b, d);
                    indices.push(c, d, b);
                }
            }
            // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
        }
        #endregion

        #region methods
        public LatheGeometry copy(LatheGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                points = source.parameters.points,
                segments = source.parameters.segments,
                phiStart = source.parameters.phiStart,
                phiLength = source.parameters.phiLength,
            };
            return this;
        }

        #endregion
    }
}
